// use std::sync::mpsc::channel;
use std::process::Command;
use std::net::Ipv4Addr;
use std::fmt;
use std::collections::HashMap;

use colored::Colorize;
use log::error;
use serde::Deserialize;
// use threadpool::ThreadPool;
use regex::Regex;
use encoding::all::GBK;
use encoding::{DecoderTrap, Encoding};
// use minihttp::request::Request;
// use crate::Loc;

use crate::security::{Cryptor, Encryptor};
use crate::ChainNet;
use crate::Chain;
use crate::ChainData;
// use crate::LocInfo;
use crate::CmdRun;


// type O<T> =  Result<T, Box<dyn std::error::Error>>;

pub fn who_am_i() -> String {
    let output = if cfg!(target_os = "windows"){
        Command::new("ipconfig")
        .output()
        .expect("failed to execute `ipconfig`")
    }else{
        match Command::new("ifconfig").output(){
            Ok(a) => a,
            _ => {
                Command::new("ip addr show").output().expect("failed to execute`ip addr show | ifconfig`")
            }
        }
    };
    let stdout = if let Ok(_stdout) =  String::from_utf8(output.stdout.clone()){
        _stdout
    }else{
        GBK.decode(&output.stdout, DecoderTrap::Ignore).unwrap()
    };
    let re = Regex::new(r#"[IipPvV46]{4}.+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"#).unwrap();
    
    let localhost_headers = [
        "192.168",
        "10.",
        "172.16",
        "127.0.0.1",
        "localhost"
    ];
    for cap in re.captures_iter(&stdout) {
        let host = cap[0].to_string();
        let mut exists_global_host = true;
        for l in localhost_headers.iter(){
            if host.starts_with(l) {
                exists_global_host = false;
                break
            }
        }
        if exists_global_host{
            if let Ok(addr) = host.parse::<Ipv4Addr>() {
                return addr.to_string()
            }
        }else{
            continue;        
        }
        
    }
    "localhost".to_string()
}


// use crate::encrypt::En
// use std::time::SystemTime;

macro_rules! dict{
    ( $($k:ident :$v:expr),* ) => {
        [
            $((stringify!($k), $v.to_string())),*
        ].iter().cloned().collect();
    };
}

#[derive(Deserialize)]
pub struct Reply {
    pub status: u32,
    pub msg: String,
}

impl Reply {
    fn try_decrypt(&mut self, cipher: &Encryptor) {
        self.msg = String::from_utf8(cipher.de_b64(&self.msg)).expect("decrypt resply error !");
    }
}
// fn mark_color

impl fmt::Display for Reply {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let msg = self
            .msg
            .replace(
                "injectable",
                "injectable".green().bold().to_string().as_str(),
            )
            .replace("not", "not".red().bold().to_string().as_str());
        if self.status == 0 {
            write!(f, "{}", msg)
        } else {
            write!(f, "{}", msg)
        }
    }
}

pub fn json_to(
    op: &str,
    data: &str,
    tp: &str,
    sub: &str,
    host: Option<&str>,
    cipher: Option<&Encryptor>,
    brd: Option<bool>,
) -> Reply {
    let mut url: String;
    let data_to: String;
    let client = reqwest::Client::new();
    let use_enc: String;

    let mut json_reply: Reply;
    let mut data_j: HashMap<&str, String>;

    if let Some(host) = host {
        if host.starts_with("http") {
            url = format!("{}", host)
        } else {
            url = format!("http://{}", host);
        }
    } else {
        url = String::from("http://localhost:54321")
    }
    url.push_str(sub);
    data_j = dict! {
        op : op,
        data : data,
        data_tp : tp
    };

    if let Some(cry) = cipher {
        use_enc = String::from("t");
        data_to = cry.en_b64(data.as_bytes());
        data_j.insert("data", data_to);
        data_j.insert("encrypt", use_enc);
    }
    if let Some(brd) = brd {
        if brd {
            data_j.insert("brd", sub.to_string());
        }
    }

    let mut res = match client.post(&url).json(&data_j).send() {
        Ok(res) => res,
        Err(e) => {
            json_reply = Reply {
                status: 1, // request error net error
                msg: format!("[req][x] {} \nreson: {}", url, e),
            };
            return json_reply;
        }
    };
    // content error
    if res.status() != 200 {
        
        json_reply = Reply {
            status: 2,
            msg: res.text().expect("text extract error"),
        };
        println!("not ok !! {}", json_reply);
        return json_reply;
    }

    // println!("{}", res.text().expect("text errr"));
    json_reply = match res.json() {
        Ok(e) => e,
        _ => {
            let mut r = match client.post(&url).json(&data_j).send() {
                Ok(a) => a,
                Err(a) => {
                    error!("err : {}", a);
                    return Reply {
                        status: 1,
                        msg: "send network error".to_string(),
                    };
                }
            };
            json_reply = Reply {
                status: 2,
                msg: r.text().expect("text extract error"),
            };
            if let Some(cry) = cipher {
                json_reply.try_decrypt(cry);
            }
            return json_reply;
        }
    };
    
    if let Some(cry) = cipher {
        json_reply.try_decrypt(cry);
    }
    // println!("{}",json_reply);
    json_reply
}


pub struct Client{
    encryptor: Encryptor,
    
}

impl Default for Client{
    fn default()   -> Self{
        Self{
            encryptor: Encryptor::new("fuck you world!"),
         
        }
    }
}

#[allow(unused)]
impl Client {
    pub fn new(pwd:&str) -> Self{
        Self{
            encryptor: Encryptor::new(pwd),
            
        }
    }
    pub fn new_hosts(pwd:&str, nodes:&Vec<String>) -> Self{
        Self{
            encryptor: Encryptor::new(pwd),
        }
    }

    pub fn alive(&self, chain:&mut Chain){
        let pool = threadpool::ThreadPool::new(7);
        let mut alive_nodes:Vec<String> = vec![];
        let (tx, rx) = std::sync::mpsc::channel::<String>();
        chain.resource.iter_mut().map(|node|{
            let node = node.clone();
            let tx = tx.clone();
            pool.execute(move||{
                let cli = reqwest::Client::new();
                if let Ok(res) = cli.get(&format!("http://{}/alive",node))
                    .send(){
                    if res.status() == 200{
                        tx.send(node.clone());
                    }else{
                        tx.send("".to_string());
                    }
                };
                
            });
        }).collect::<Vec<_>>();

        for i in 0..chain.resource.len(){
            let n = rx.recv().expect("recv error!");
            if n.len() > 0{
                alive_nodes.push(n);
            }
        }
        chain.resource = alive_nodes;

    }

    // pub fn patch(&self, chain:&Chain) -> Chain{

    //     // chian
    // }

    pub fn nodes(&self, chain:&Chain) -> Option<Vec<String>>{
        
        let content = chain.to_json();
        let mut if_brd = false;
        let repl = json_to(
            "nodes",
            &content,
            &chain.action,
            "/chain",
            Some(&chain.loc.host),
            Some(&self.encryptor),
            Some(if_brd)
        );
        if let Ok(a) = serde_json::from_str(&repl.msg){
            Some(a)
        }else{
            None
        }
    }
    
    fn records(&self) -> Vec<String>{
        Vec::new()
    }
}
impl Clone for Client{
    fn clone(&self) -> Self{
        Self{
            encryptor: Encryptor::new(&self.encryptor.raw_key),
            // used_node: self.used_node.clone()
        }
    }

}


impl ChainNet for Client{
    fn action(&self, chain:&Chain) -> Option<Chain>{
        
        let content = chain.to_json();
        let mut if_brd = false;
        let task_path = chain.task_path();
        if chain.is_this_node(){
            let mut chain = chain.to_owned();
            log::info!("{} : {}","local", chain.action.green());
            if chain.action == "run"{
                chain.run();
                Chain::wait();
            }
            // pass result to task start node.
            if chain.sub_chains.len() == 0 && chain.loc.host != chain.start_node.host{
                if let Some(content) = chain.task_path().file_open_str(){
                    json_to(
                        "circle",
                        &content,
                        &format!("{}.{}",chain.start_node.uid, chain.loc.uid),
                        "/chain",
                        Some(&chain.start_node.host),
                        Some(&self.encryptor),
                        Some(if_brd)
                    );
                }
                
            }

            let nodes = chain.resource.clone();
            if chain.sub_chains.len() > 0 && nodes.len() >0 && chain.task.output_tp > 0{
                
                let pool = threadpool::ThreadPool::new(5);
                let mut map:HashMap<String, String> = HashMap::new();
                for ch in chain.sub_chains.iter_mut(){
                    if let Some(out) = task_path.file_open_str(){
                        let _ = out.split("\n").enumerate().map(|(no,arg)| {
                            let host:String = (&nodes)[no % nodes.len()].clone();
                            let new_task = ch.task.args(&vec![arg.clone()]).clone().new_chain();
                            let mut new_chain = ch.clone();
                            new_chain.loc.host = host.clone();
                            new_chain.task = new_task.task;
                            log::info!("{} -> {}", &new_chain.loc.uid, &host);
                            map.insert(new_chain.loc.uid.clone(), host);
                            let client = self.clone();
                            pool.execute(move||{
                                client.action(&new_chain);
                            });
                        }).collect::<Vec<_>>();
                    }    
                }
                if map.len() > 0{
                    chain.task_action_record(&map);
                }
                
            }
            

            
            None
        }else{
            log::info!("{} : {}", chain.loc, chain.action.green());
            if chain.task.output_tp & crate::OUTPUT_TO_MANY_BRD != 0{
                if_brd = false
            }
            let repl = json_to(
                "chain",
                &content,
                &chain.action,
                "/chain",
                Some(&chain.loc.host),
                Some(&self.encryptor),
                Some(if_brd)
            );
            log::info!("repl : {}", repl.msg);
            let a = repl.msg.chain_load_json();
            a
        }
        
        
    }
}
